package com.xxmw.transaction.common.utils.bourse;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xxmw.transaction.common.excep.service.TransactionException;
import com.xxmw.transaction.common.utils.HmacUtil;
import com.xxmw.transaction.common.utils.http.HttpClientUtil;
import com.xxmw.transaction.common.utils.http.HttpHeader;
import com.xxmw.transaction.common.utils.http.RestTemplateUtil;
import com.xxmw.transaction.common.utils.symbolFormat.SymbolFormat;
import com.xxmw.transaction.enumeration.BourseEnum;
import com.xxmw.transaction.model.app.BourseApi;
import com.xxmw.transaction.model.app.BourseOrder;
import com.xxmw.transaction.vo.BalanceVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * https://www.okex.com/docs/zh/
 * @author yamoo
 * @since 2020-2-10 22:16
 */
@Slf4j
@Component
public class OkexUtil {

    /**
     * 市价下单
     * SIZE 买入或卖出的数量 买入必填   notional 买入金额 卖出必填
     */
    private static final String ORDER_MARKEY_TYPE = "market";

    /**
     * 限价下单
     */
    private static final String ORDER_LIMIT_TYPE = "limit";

    /**
     * 买入
     */
    private static final String BUY_TYPE = "buy";

    /**
     * 卖出
     */
    private static final String SELL_TYPE = "sell";

    private static final String POST = "POST";

    private static final String GET = "GET";

    /**
     * ok网基础接口
     */
    private static final String BASE_URL = "https://www.okex.com";
    /**
     * ok网下单接口 https://www.okex.com/api/spot/v3/orders
     */
    private static final String ORDER_URL = "/api/spot/v3/orders";

    /**
     * 批量下单
     */
    private static final String BATCH_ORDER_PATH = "/api/spot/v3/batch_orders";

    /**
     * ok网取消订单接口
     */
    private static final String CANCEL_ORDER_URL = "/api/spot/v3/cancel_orders/";

    /**
     * 查询订单详情
     */
    private static final String ORDER_INFO_URL = "/api/spot/v3/orders/";

    /**
     * 查询账户余额信息
     */
    private static final String ACCOUNT_INFO_URL = "/api/account/v3/wallet";

    /**
     * 查询单币种余额信息
     */
    private static final String ACCOUNT_URL = "/api/spot/v3/accounts/%s";

    /**
     * 查询单币种余额信息
     */
    private static final String ACCOUNT_VALUATION_URL = "/api/account/v3/asset-valuation?account_type=1&valuation_currency=cny";

    /**
     * KEY 请求头
     */
    private static final String  KEY_HEADER = "OK-ACCESS-KEY";

    /**
     *签名 请求头
     */
    private static final String SIGN_HEADER = "OK-ACCESS-SIGN";

    /**
     * 时间戳请求头
     */
    private static final String TIMESTAMP_HEADER = "OK-ACCESS-TIMESTAMP";

    /**
     * 口令请求头
     */
    private static final String PASSPHRASE_HEADER = "OK-ACCESS-PASSPHRASE";

    private static final SymbolFormat symbolFormat = BourseEnum.OKEX.getSymbolFormat();

    /**
     * 查询单个账户余额
     * @param api 交易所账户信息
     */
    public static BalanceVo getBalance (BourseApi api, String currency) {
        JSONObject jsonObject = balanceObject(api, currency);
        BalanceVo balanceVo = new BalanceVo();
        if (jsonObject != null) {
            balanceVo.setFrozen(jsonObject.getBigDecimal("hold"));
            balanceVo.setAvailable(jsonObject.getBigDecimal("available"));
        }
        balanceVo.setTotal(balanceVo.getAvailable().add(balanceVo.getFrozen()));
        return balanceVo;
    }

    public static Map<String, BalanceVo> getBalance (BourseApi api, List<String> currency) {
        Map<String, BalanceVo> map = new HashMap<>(16);
        if (currency.size() <= 0) {
            return map;
        }
        for (String s : currency) {
            map.put(s, getBalance(api, s));
        }
        return map;
    }

    public static JSONObject balanceObject (BourseApi api, String currency) {
        StringBuilder body = new StringBuilder();
        String accountPath = String.format(ACCOUNT_URL, currency);
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        body.append(dateTime).append(GET).append(accountPath);
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());
        HttpHeader headers = new HttpHeader();
        headers.add(KEY_HEADER, api.getAccessKey());
        headers.add(SIGN_HEADER,sign);
        headers.add(TIMESTAMP_HEADER,dateTime);
        headers.add(PASSPHRASE_HEADER,api.getPassphrase());
        String res = HttpClientUtil.get(BASE_URL + accountPath, headers);
        JSONObject jsonObject = JSONObject.parseObject(res);
        // log.info("OK返回的数据为:{}", res);
        if (jsonObject != null) {
            return jsonObject;
        }
        throw new TransactionException("Okex获取账户余额异常");
    }

    public static void assetValuation (BourseApi api) {
        StringBuilder body = new StringBuilder();
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        body.append(dateTime).append(GET).append(ACCOUNT_VALUATION_URL);
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());
        HttpHeader headers = new HttpHeader();
        headers.add(KEY_HEADER, api.getAccessKey());
        headers.add(SIGN_HEADER,sign);
        headers.add(TIMESTAMP_HEADER,dateTime);
        headers.add(PASSPHRASE_HEADER,api.getPassphrase());
        String res = HttpClientUtil.get(BASE_URL + ACCOUNT_VALUATION_URL, headers);
        JSONObject jsonObject = JSONObject.parseObject(res);
        log.info("OK返回的cny数据为:{}", res);
    }

    /**
     * Okex 下单接口
     * @param api 交易所账户信息
     * @param order 订单
     * @return
     */
    public static void order(BourseApi api, BourseOrder order) {
        StringBuilder body = new StringBuilder();
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        JSONObject params = getOrderBody(order);
        body.append(dateTime).append(POST).append(ORDER_URL).append(params.toJSONString());
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());

        HttpHeader header = new HttpHeader();
        header.add(KEY_HEADER, api.getAccessKey());
        header.add(SIGN_HEADER,sign);
        header.add(TIMESTAMP_HEADER, dateTime);
        header.add(PASSPHRASE_HEADER,api.getPassphrase());
        log.info("ok下单传输的下单的参数列表为：{}",params.toJSONString());
        //下单
        String res = HttpClientUtil.post(BASE_URL + ORDER_URL, header, params.toJSONString());
        log.info("ok下单的结果为：{}",res);
        //返回结果处理
        JSONObject jsonObject = JSONObject.parseObject(res);
        if (jsonObject != null && jsonObject.getBoolean("result") != null) {
            if (jsonObject.getBoolean("result")) {
                order.setStatus(BourseOrder.Status.PUBLISHED.value());
                order.setOutId(jsonObject.getString("order_id"));
            } else {
                order.setStatus(BourseOrder.Status.PUBLISH_ERROR.value());
                order.setRemark(jsonObject.getString("error_message"));
                log.error("OK订单" + order.getId() + "下单失败： {}", res);
            }
        } else {
            order.setStatus(BourseOrder.Status.PUBLISH_ERROR.value());
            log.error("OK下单接口返回信息为空");
        }
    }

    /**
     * okex批量下单
     * @param api
     * @param orders
     * @param <T>
     * @throws Exception
     */
    public static <T extends BourseOrder> void order (BourseApi api, List<T> orders) {

        if (null == orders || orders.size() == 0) {
            return;
        }

        if (orders.size() == 1) {
            order(api, orders.get(0));
            return;
        }

        JSONArray paramArray = new JSONArray();
        for (BourseOrder order : orders) {
            JSONObject params = getOrderBody(order);
            paramArray.add(params);
        }
        //只处理一个交易对的情况
        String symbol = orders.get(0).getSymbol();

        String jsonParam = paramArray.toJSONString();

        StringBuilder body = new StringBuilder();
        String timestamp = getTimestamp();
        body.append(timestamp).append(POST).append(BATCH_ORDER_PATH).append(jsonParam);

        log.info("签名字符串: {}", body.toString());

        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());

        HttpHeader header = new HttpHeader();
        header.add(KEY_HEADER, api.getAccessKey());
        header.add(SIGN_HEADER,sign);
        header.add(TIMESTAMP_HEADER, timestamp);
        header.add(PASSPHRASE_HEADER,api.getPassphrase());
        //下单
        String res = HttpClientUtil.post(BASE_URL + BATCH_ORDER_PATH, header, jsonParam);
        log.info("ok下单的结果为：{}",res);
        //返回结果处理
        //{"eth-usdt":[{"client_oid":"okex1232628237102518273","error_code":"33017","error_message":"Greater than the maximum available balance","order_id":"-1","result":false},{"client_oid":"okex1232628237102518274","error_code":"33017","error_message":"Greater than the maximum available balance","order_id":"-1","result":false},{"client_oid":"okex1232628237106712577","error_code":"33017","error_message":"Greater than the maximum available balance","order_id":"-1","result":false},{"client_oid":"okex1232628237106712578","error_code":"33017","error_message":"Greater than the maximum available balance","order_id":"-1","result":false}]}
        JSONObject jsonObject = JSONObject.parseObject(res);
        if (jsonObject != null) {
            JSONArray jsonArray = jsonObject.getJSONArray(symbolFormat.symbolFormat(orders.get(0).getBaseCurrency(), orders.get(0).getQuoteCurrency()).toLowerCase());
            Map<Long, T> orderMap = orders.stream().collect(HashMap::new, (map, order) -> map.put(order.getId(), order), Map::putAll);
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject orderRes = jsonArray.getJSONObject(i);
                Long clientOid = Long.valueOf(orderRes.getString("client_oid").substring(4));
                T clientOd = orderMap.get(clientOid);
                if (orderRes.getBoolean("result")) {
                    clientOd.setStatus(1);
                    clientOd.setOutId(orderRes.getString("order_id"));
                } else {
                    clientOd.setStatus(4);
                    clientOd.setRemark(orderRes.getString("error_message"));
                }
            }
        } else {
            log.error("ok批量下单无响应， 未处理订单状态");
        }
    }

    /**
     * 取消订单
     * @param api
     * @return true表示已经取消成功 false表示取消失败
     */
    public static boolean cancelOrder (BourseApi api, BourseOrder order) {
        boolean isCancel = false;
        StringBuilder body = new StringBuilder();
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        JSONObject params = new JSONObject();
        params.put("instrument_id", symbolFormat.symbolFormat(order.getBaseCurrency(), order.getQuoteCurrency()));
        params.put("client_oid", order.getId());
        body.append(dateTime).append(POST).append(CANCEL_ORDER_URL + order.getId()).append(params.toJSONString());
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());

        HttpHeader header = new HttpHeader();
        header.add(KEY_HEADER, api.getAccessKey());
        header.add(SIGN_HEADER,sign);
        header.add(TIMESTAMP_HEADER, dateTime);
        header.add(PASSPHRASE_HEADER,api.getPassphrase());
        //下单
        String res = HttpClientUtil.post(BASE_URL+ORDER_URL + order.getId(), header, params.toJSONString());
        log.info("ok取消订单的结果为：{}",res);
        //返回结果处理
        if (null != res) {
            JSONObject jsonObject = JSONObject.parseObject(res);
            if (jsonObject.getBoolean("result")) {
                order.setStatus(3);
                isCancel = true;
            }
        }
        return isCancel;
    }

    /**
     * 账户信息
     * @param api
     * @return
     */
    public static String accountInfo (BourseApi api) {
        StringBuilder body = new StringBuilder();
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        body.append(dateTime).append(POST).append(ACCOUNT_INFO_URL);
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());
        HttpHeaders headers = new HttpHeaders();
        headers.add(KEY_HEADER, api.getAccessKey());
        headers.add(SIGN_HEADER,sign);
        headers.add(TIMESTAMP_HEADER,dateTime);
        headers.add(PASSPHRASE_HEADER,api.getPassphrase());
        ResponseEntity<String> response = RestTemplateUtil.getForEntity(BASE_URL + ACCOUNT_INFO_URL, headers, String.class);
        String res = response.getBody();
        log.info("查询到OK账号余额:{}",res);
        return res;
    }

    /**
     * 获取订单详情
     * @return
     */
    public static void orderInfo(BourseApi api, BourseOrder order) {
        StringBuilder body = new StringBuilder();
        //获取当前时间 对应ok 时间戳
        String dateTime = getTimestamp();
        body.append(dateTime).append(GET).append(ORDER_INFO_URL + order.getOutId() + "?instrument_id=" + symbolFormat.symbolFormat(order.getBaseCurrency(),order.getQuoteCurrency()));
        //获取签名
        String sign = sign(body.toString(), api.getSecretKey());

        HttpHeader header = new HttpHeader();
        header.add(KEY_HEADER, api.getAccessKey());
        header.add(SIGN_HEADER,sign);
        header.add(TIMESTAMP_HEADER, dateTime);
        header.add(PASSPHRASE_HEADER,api.getPassphrase());
        //获取订单详情
        String res = HttpClientUtil.get(BASE_URL + ORDER_INFO_URL + order.getOutId() + "?instrument_id=" + symbolFormat.symbolFormat(order.getBaseCurrency(), order.getQuoteCurrency()), header);
        log.info("ok查询订单详情的结果为：{}",res);
        //返回结果处理
        JSONObject jsonObject = JSONObject.parseObject(res);
        if (jsonObject != null) {
            String status = jsonObject.getString("state");
            String avg = jsonObject.getString("price_avg");
            order.setStatus(getOrderStatus(status));
            if (!StringUtils.isEmpty(avg)){
                order.setAvgPrice(new BigDecimal(avg));
                order.setDealNum(jsonObject.getBigDecimal("filled_size"));
            }
        } else {
            log.error("Ok订单查询无响应");
        }
    }

    public static Integer getOrderStatus (String orderStatus) {
        Integer status = null;
        switch (orderStatus) {
            case "-2":
                status = BourseOrder.Status.PUBLISH_ERROR.value();
                break;
            case "-1":
                status = BourseOrder.Status.CANCLED.value();
                break;
            case "0":
                status = BourseOrder.Status.PUBLISHED.value();
                break;
            case "1":
                status = BourseOrder.Status.DEAL_PART.value();
                break;
            case "2":
                status = BourseOrder.Status.DEALED.value();
                break;
            case "3":
                status = BourseOrder.Status.PUBLISHED.value();
                break;
            case "4":
                status = BourseOrder.Status.CANCELING.value();
                break;
            default:
                break;
        }
        return status;
    }


    /*public static String getSymbol(String symbol) {
        switch (symbol) {
            case "btcusdt":
                return "BTC-USDT";
            case "ethusdt":
                return "ETH-USDT";
            case "ltcusdt":
                return "LTC-USDT";
            case "htusdt":
                return "HT-USDT";
            case "BTC-USDT":
                return "btcusdt";
            case "ETH-USDT":
                return "ethusdt";
            case "LTC-USDT":
                return "ltcusdt";
            case "HT-USDT":
                return "htusdt";
            default:
                return null;
        }
    }*/

    /**
     * OK签名方法
     * @param body 待签名数据
     * @return
     */
    private static String sign (String body, String secret) {
        return HmacUtil.huobiSign(body, secret);
    }

    public static String getTimestamp () {
        Date date = new Date();
        TimeZone tz = TimeZone.getTimeZone("UTC");
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        format.setTimeZone(tz);
        //获取当前时间
        String dateTime = format.format(date);
        return dateTime;
    }

    public static JSONObject getOrderBody (BourseOrder order) {
        //参数列表
        JSONObject params = new JSONObject();
        params.put("client_oid","okex" + order.getId());
        Integer tradeType = order.getTradeType();
        switch (tradeType) {
            case 1:
                params.put("type",ORDER_MARKEY_TYPE);
                params.put("side",BUY_TYPE);
                //BTC-USDT   usdt 数量
                params.put("notional",order.getNum().multiply(order.getPrice()).toString());
                break;
            case 2:
                params.put("type",ORDER_MARKEY_TYPE);
                params.put("side",SELL_TYPE);
                //卖出币种的数量
                params.put("size",order.getNum().toString());
                break;
            case 3:
                params.put("type",ORDER_LIMIT_TYPE);
                params.put("side",BUY_TYPE);
                params.put("size",order.getNum().toString());
                params.put("price",order.getPrice().toString());
                break;
            case 4:
                params.put("type",ORDER_LIMIT_TYPE);
                params.put("side",SELL_TYPE);
                params.put("size",order.getNum().toString());
                params.put("price",order.getPrice().toString());
                break;
            default:
        }
        params.put("instrument_id",symbolFormat.symbolFormat(order.getBaseCurrency(),order.getQuoteCurrency()));
        return params;
    }
}
